﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;

namespace org.mayb.Fixtures
{
    public class PropertyHash
    {
        private readonly Dictionary<string, object> props = new Dictionary<string, object>();

        internal void ProcessHash(object obj, Type objectType)
        {
            foreach (KeyValuePair<string, object> prop in props)
            {
                string propertyName = prop.Key;
                PropertyInfo pi = objectType.GetProperty(propertyName);
                if (pi == null || !pi.CanWrite)
                    throw new Exception(string.Format("Type {0} does not have publically set-able property {1}.", objectType.Name, propertyName));

                object value = prop.Value;

                if (value is string)
                    value = Fixtures.GetTypedValue(pi.PropertyType, (string)value);
                else if (value is ObjectReference)
                {
                    value = Fixtures.GetFixture(((ObjectReference)value).Reference);
                    if (!(pi.PropertyType == value.GetType() || value.GetType().IsSubclassOf(pi.PropertyType)))
                        throw new FixtureException(string.Format("Value {0} with type {1} not assignable to property {2} (Type {3}) of type {4}.", value, value.GetType().Name, propertyName, pi.PropertyType.Name, objectType.Name));
                }
                else if (value is ArrayInfo)
                {
                    object collection = pi.GetValue(obj, null);
                    ArrayInfo array = (ArrayInfo)value;

                    Type valueType = typeof (string);
                    if (pi.PropertyType.IsArray)
                        valueType = pi.PropertyType.GetElementType();
                    else if (pi.PropertyType.IsGenericType)
                        valueType = pi.PropertyType.GetGenericArguments()[0];

                    if (collection == null)
                    {
                        if (pi.PropertyType.IsArray)
                        {
                            collection = Array.CreateInstance(valueType, array.Count);
                            pi.SetValue(obj, collection, null);
                        }
                        else
                        {
                            collection = Fixtures.CreateInstance(pi.PropertyType);
                            pi.SetValue(obj, collection, null);
                        }
                    }
                    else
                    {
                        if (pi.PropertyType.IsArray)
                        {
                            if (((Array)collection).Length < array.Count)
                            {
                                collection = Array.CreateInstance(valueType, array.Count);
                                pi.SetValue(obj, collection, null);
                            }
                        }
                    }

                    if(pi.PropertyType.IsArray)
                    {
                        array.ProcessArray((Array)collection, valueType);
                    }
                    else
                    {
                        array.ProcessArray(collection, valueType);
                    }

                    continue;
                }

                Fixtures.SetProperty(obj, value, pi);
            }
        }

        public void AddProperty(string name, object value)
        {
            props.Add(name, value);
        }

        public List<string> FindRefernces()
        {
            List<string> refs = new List<string>();
            this.FindRefs(refs);
            return refs;
        }

        internal void FindRefs(List<string> refs)
        {
            foreach (KeyValuePair<string, object> kvp in this.props)
            {
                if (kvp.Value is ObjectReference)
                {
                    refs.Add(((ObjectReference)kvp.Value).Reference);
                }
                else if (kvp.Value is ArrayInfo)
                {
                    ((ArrayInfo)kvp.Value).FindRefs(refs);
                }
                else if (kvp.Value is PropertyHash)
                {
                    ((PropertyHash)kvp.Value).FindRefs(refs);
                }
            }
        }
    }
}
